﻿using System;
using System.ComponentModel;
using System.Device.Location;
using System.Runtime.Serialization;
using System.Windows;
using System.Windows.Data;
using System.Windows.Media;
using System.Windows.Shapes;

using Microsoft.Phone.Controls;
using Microsoft.Phone.Controls.Maps;


namespace Coding4Fun.WheresMyCar
{
	[DataContract]
	public partial class MapPage : PhoneApplicationPage, INotifyPropertyChanged
	{
        private const double MetersPerPixel = 156543.04; // meters/pixel for base map
        
        private GeoCoordinateWatcher _gps;
        private Map _map;
	    private RouteHelper helper;
        private Shape circle = null;
        private MapLayer circleLayer;
		private bool _locationSet;
        // Constructor
        public MapPage()
        {
            InitializeComponent();

            Settings = WheresTheCarSettings.Instance;
            DataContext = this;

			_map = Utility.CreateMapWithPushpins();
            _map.MapZoom += _map_MapZoom;
            helper = new RouteHelper(_map, Settings);

			MapGrid.Children.Add(_map);

            circle = new Ellipse()
            {
                Stroke = new SolidColorBrush(Colors.LightGray),
                StrokeThickness = 2,
                Fill = (SolidColorBrush) Resources["PhoneAccentBrush"],
				Opacity = .4
            };
            
            circle.SetBinding(MapLayer.PositionProperty, new Binding("CurrentLocation"));

            circleLayer = new MapLayer();
            _map.Children.Add(circleLayer);

            _gps = ((App)Application.Current).GPS;
            _gps.StatusChanged += GpsStatusChanged;
            _gps.PositionChanged += GpsPositionChanged;

			if (_gps.Position != null)
				GpsPositionChanged(this, null);
		}

        void _map_MapZoom(object sender, MapZoomEventArgs e)
        {
            UpdateAccuracyCircle();
        }

		private void UpdateAccuracyCircle()
		{
            // Call on dispatch thread...
            if( !Dispatcher.CheckAccess()) Dispatcher.BeginInvoke(UpdateAccuracyCircle);

			if (circleLayer.Children.Contains(circle))
			    circleLayer.Children.Remove(circle);

			// http://msdn.microsoft.com/en-us/library/aa940990.aspx
			var resolution = MetersPerPixel*Math.Cos(CurrentLocation.Latitude*Math.PI/180.0)/
			                    Math.Pow(2.0, _map.ZoomLevel);

		    var diameter = Math.Min(Accuracy / resolution, 50);

			circle.Width = diameter;
			circle.Height = diameter;
			circleLayer.AddChild(circle, CurrentLocation, PositionOrigin.Center);

			// Only show the accuracy indicator if accuracy is higher than 3km,
			// the circle is within the size of the screen, and bigger than 15px
			circle.Visibility = (Accuracy <= 3000 && diameter < ActualWidth
			                       	? Visibility.Visible
			                       	: Visibility.Collapsed);
		}

		#region Event handlers

        void GpsStatusChanged(object sender, GeoPositionStatusChangedEventArgs e)
        {
			if (_gps.Status == GeoPositionStatus.Ready)
			{
				CurrentLocation = _gps.Position.Location;
				if(!_locationSet)
					this.MapCenter = _gps.Position.Location;
				_locationSet = true;
			}

        	Status = e.Status.ToString();
        }

        void GpsPositionChanged(object sender, GeoPositionChangedEventArgs<GeoCoordinate> e)
        {
            UpdateLocation();
        }

        void UpdateLocation()
        {
	        if (_gps.Position == null || _gps.Position.Location == null) return;

            var prevAccuracy = this.Accuracy;
			Accuracy = Math.Round(_gps.Position.Location.HorizontalAccuracy, 0);
            
            CurrentLocation = _gps.Position.Location;
            Settings.HeadingToCar = Utility.CalculateHeading(CurrentLocation, Settings.ParkingSpot);

            if( prevAccuracy != this.Accuracy) UpdateAccuracyCircle();
            
            if (Utility.IsCoordNullOrUnknown(Settings.ParkingSpot) && Utility.IsCoordNullOrUnknown(CurrentLocation))
                this.MapCenter = _gps.Position.Location;
            
            // Re-route only if accuracy has increased);
            if (this.Accuracy < prevAccuracy && helper != null)
            {
                helper.CalculateRouteToCar(CurrentLocation);
            }
        }

        protected override void OnNavigatedTo(System.Windows.Navigation.NavigationEventArgs e)
		{
			base.OnNavigatedTo(e);

            if (!Utility.IsCoordNullOrUnknown(Settings.ParkingSpot))
                this.MapCenter = Settings.ParkingSpot;
            else if (!Utility.IsCoordNullOrUnknown(CurrentLocation))
                this.MapCenter = this.CurrentLocation;

            if (helper != null )
                helper.CalculateRouteToCar(CurrentLocation);
        }

		protected override void OnNavigatedFrom(System.Windows.Navigation.NavigationEventArgs e)
		{
			base.OnNavigatedFrom(e);
			Settings.Save();
		}

		private void appbar_ShowMe_Click(object sender, EventArgs e)
		{
            if (!Utility.IsCoordNullOrUnknown(CurrentLocation))
                this.MapCenter = this.CurrentLocation;
				_map.ZoomLevel = 15;
		}

        private void appbar_ShowCar_Click(object sender, EventArgs e)
        {
            if (!Utility.IsCoordNullOrUnknown(Settings.ParkingSpot))
            {
                this.MapCenter = Settings.ParkingSpot;
                _map.ZoomLevel = 15;
            }
        }

		private void appbar_Route_Click(object sender, EventArgs e)
		{
			helper.CalculateRouteToCar(CurrentLocation);
		}

		#endregion

		#region Public properties

        #region Settings (INotifyPropertyChanged Property)
        private WheresTheCarSettings _settings;

        public WheresTheCarSettings Settings
        {
            get { return _settings; }
            set
            {
                _settings = value;
                RaisePropertyChanged("Settings");
            }
        }
        #endregion

        #region RouteLayer (INotifyPropertyChanged Property)
        private MapLayer _routeLayer;
        public MapLayer RouteLayer
        {
            get { return _routeLayer; }
            set
            {
                _routeLayer = value;
                RaisePropertyChanged("RouteLayer");
            }
        }
        #endregion

        #region Status (INotifyPropertyChanged Property)
        private string _status;

		public string Status
		{
			get { return _status; }
			set
			{
				_status = value;
				RaisePropertyChanged("Status");
			}
		}
		#endregion

		#region Accuracy (INotifyPropertyChanged Property)
		private double _accuracy;

		public double Accuracy
		{
			get { return _accuracy; }
			set
			{
				_accuracy = value;
				RaisePropertyChanged("Accuracy");
			}
		}
		#endregion

        #region MapCenter (INotifyPropertyChanged Property)
        private GeoCoordinate _mapCenter;

        public GeoCoordinate MapCenter
        {
            get { return _mapCenter; }
            set
            {
                _mapCenter = value;
                RaisePropertyChanged("MapCenter");
            }
        }
        #endregion

        #region CurrentLocation (INotifyPropertyChanged Property)
        private GeoCoordinate _currentLocation;
        public GeoCoordinate CurrentLocation
        {
            get { return _currentLocation; }
            set
            {
                _currentLocation = value;
                RaisePropertyChanged("CurrentLocation");
            }
        }
        #endregion

        #endregion

		#region INotifyPropertyChanged Members

		public event PropertyChangedEventHandler PropertyChanged;

		protected void RaisePropertyChanged(string propertyName)
		{
			Dispatcher.BeginInvoke(() =>
			{
				if (PropertyChanged != null)
				{
					PropertyChanged(this, new PropertyChangedEventArgs(propertyName));
				}
			});
		}
		#endregion

	}

}

//static readonly double FullCircleDegrees = 360d;
//static readonly double HalfCircleDegrees = FullCircleDegrees / 2d;
//static readonly double DegreesToRadians = Math.PI / HalfCircleDegrees;
//static readonly double RadiansToDegrees = 1 / DegreesToRadians;

//public GeoCoordinate GetCircumferencePoint(GeoCoordinate center, double radius, double azimuth)
//{
//    var lat1 = center.Latitude * DegreesToRadians;
//    var lng1 = center.Longitude * DegreesToRadians;
//    var lat = Math.Asin((Math.Sin(lat1) * Math.Cos(radius)) + Math.Cos(lat1) * Math.Sin(radius) * Math.Cos(azimuth * DegreesToRadians));
//    var lng = 0d;
//    if (Math.Cos(lat) == 0)
//    {
//        lng = lng1;
//    }
//    else
//    {
//        lng = ((lng1 + Math.PI - Math.Asin(Math.Sin(azimuth * DegreesToRadians) * Math.Sin(radius) / Math.Cos(lat1))) % (2 * Math.PI)) - Math.PI;
//    }
//    return new GeoCoordinate(lat * RadiansToDegrees, lng * RadiansToDegrees);
//}
